// Copyright (c) 2023 Presto Labs Pte. Ltd.
// Author: fengyang

#pragma once

#include <map>
#include <string>

#include <boost/date_time/posix_time/posix_time.hpp>
#include <boost/stacktrace.hpp>
#include <simdjson.h>

#include "coin/proto/coin_market_enums.pb.h"
#include "coin/proto/coin_order_enums.pb.h"
#include "coin2/base/conversion.h"
#include "coin2/base/time.h"
#include "coin2/exchange/base/feed/update.h"
#include "coin2/exchange/base/feed_util/book_builder.h"
#include "coin2/exchange/base/feed_util/trade.h"
#include "coin2/exchange/base/feed_util/kline.h"
#include "coin2/exchange/base/market/enums.h"

namespace coin2::exchange::mercado::feed_v4 {
namespace impl {
using coin2::exchange::base::market::ExchangeType;
using coin2::exchange::base::market::MarketType;
using coin2::exchange::base::feed::FeedUpdateManager;
using namespace coin::proto;


inline coin2::exchange::feed::TradeSide GetTradeSide(std::string_view side) {
  if (side == "buy") {
    return coin2::exchange::feed::TradeSide::TRADE_BUY_SIDE;
  } else if (side == "sell") {
    return coin2::exchange::feed::TradeSide::TRADE_SELL_SIDE;
  } else {
    return coin2::exchange::feed::TradeSide::TRADE_UNKNOWN_SIDE;
  }
}


class MercadoFeedUpdateParser {
 public:
  static void ParseBook(
      BookBuilder* book,
      simdjson::ondemand::object& msg,
      int64_t ts) {
    auto tx = book->NewTransaction(ts);
    tx.Clear();
    double result[2] = {};
    for (auto && pq : simdjson::ondemand::array(msg["asks"])) {
      int i = 0;
      for (auto && p : pq)
      {
        if (i > 1) break;
        result[i++] = p.get_double();
      }
      tx.UpdateAsk(result[0], result[1]);
    }
    for (auto && pq : simdjson::ondemand::array(msg["bids"])) {
      int i = 0;
      for (auto && p : pq)
      {
        if (i > 1) break;
        result[i++] = p.get_double();
      }
      tx.UpdateBid(result[0], result[1]);
    }
  }

  static void ParseTrade(
      simdjson::ondemand::object& msg,
      Trade* trade,
      int64_t timestamp) {
    trade->timestamp = timestamp;
    trade->fill_qty = msg["amount"].get_double();
    trade->price = msg["price"].get_double();
    trade->side = GetTradeSide(msg["type"].get_string());
  }

  static fastfeed::proto::KlineInterval ParseKlineInterval(std::string_view interval) {
    static std::map<std::string, fastfeed::proto::KlineInterval> interval_map = {
      {"1m", fastfeed::proto::KLINE_INTERVAL_1MIN},
      {"3m", fastfeed::proto::KLINE_INTERVAL_3MIN},
      {"5m", fastfeed::proto::KLINE_INTERVAL_5MIN},
      {"15m", fastfeed::proto::KLINE_INTERVAL_15MIN},
      {"30m", fastfeed::proto::KLINE_INTERVAL_30MIN},
      {"1h", fastfeed::proto::KLINE_INTERVAL_1HOUR},
      {"1d", fastfeed::proto::KLINE_INTERVAL_1DAY}
    };

    auto [iter, succ] = interval_map.emplace(interval, fastfeed::proto::KLINE_INTERVAL_INVALID);
    return iter->second;
  }

  static bool ParseKlineRest(
      simdjson::ondemand::object& msg,
      Klines* klines,
      fastfeed::proto::KlineInterval interval,
      int64_t timestamp) {

      std::vector<Kline*> kline_list;
      bool to_publish = false;
      simdjson::ondemand::array t_array = msg["t"].get_array();
      for (auto&& t : t_array) {
        int64_t open_timestamp = t.get_int64() * 1'000'000'000LL;
        auto result = klines->AddKline(open_timestamp);
        if (!result) return false;

        Kline* kline;
        bool freezed;
        std::tie(kline, freezed) = result.value();
        to_publish = to_publish || freezed;
        kline->open_timestamp = open_timestamp;
        kline->timestamp = timestamp;
        kline->interval = interval;
        kline_list.push_back(kline);
      }

      simdjson::ondemand::array o_array = msg["o"].get_array();
      int i = 0;
      for (auto&& o : o_array) {
        if (i >= kline_list.size()) break;
        Kline* kline = kline_list[i];
        kline->open = stod(o.get_string());
        i++;
      }

      simdjson::ondemand::array h_array = msg["h"].get_array();
      i = 0;
      for (auto&& h : h_array) {
        if (i >= kline_list.size()) break;
        Kline* kline = kline_list[i];
        kline->high= stod(h.get_string());
        i++;
      }

      simdjson::ondemand::array l_array = msg["l"].get_array();
      i = 0;
      for (auto&& l : l_array) {
        if (i >= kline_list.size()) break;
        Kline* kline = kline_list[i];
        kline->low= stod(l.get_string());
        i++;
      }

      simdjson::ondemand::array c_array = msg["c"].get_array();
      i = 0;
      for (auto&& c : c_array) {
        if (i >= kline_list.size()) break;
        Kline* kline = kline_list[i];
        kline->close= stod(c.get_string());
        i++;
      }

      simdjson::ondemand::array v_array = msg["v"].get_array();
      i = 0;
      for (auto&& v : v_array) {
        if (i >= kline_list.size()) break;
        Kline* kline = kline_list[i];
        kline->volume= stod(v.get_string());
        i++;
      }

    return to_publish;
  }
};

}  // namespace impl

using impl::MercadoFeedUpdateParser;

}  // namespace coin2::exchange::Mercado::feed_v4
